Aller au contenu principal

Classes

Introduction : Qu'est-ce qu'une Classe ?

En Java, une classe est un modèle (ou plan) qui définit la structure et le comportement d'un type d'objet. C'est le fondement de la Programmation Orientée Objet (POO).

Une classe sert de modèle réutilisable pour créer plusieurs objets ayant les mêmes caractéristiques de base.

Analogie du modèle et des instances

Imaginez un modèle (plan architectural) pour construire des maisons :

  • La classe = le modèle (le plan)
  • Les objets = les maisons construites selon ce modèle (les instances)

Chaque maison (objet) suit le même modèle (classe) mais peut avoir des caractéristiques différentes (couleur, meubles, décoration).

Définition formelle

Une classe est un ensemble composé de :

  • Attributs (ou données membres) : les caractéristiques de l'objet
  • Méthodes : les actions que l'objet peut effectuer
  • Constructeurs : méthodes spéciales pour créer et initialiser les objets

Première Classe Simple : La Voiture

Commençons avec une classe basique représentant une voiture :

public class Voiture {
// Attributs (caractéristiques de la voiture)
String marque;
String modele;
int annee;
String couleur;

// Méthodes (actions que la voiture peut faire)
public void demarrer() {
System.out.println("La voiture démarre : Vroom!");
}

public void klaxonner() {
System.out.println("Beep beep!");
}

public void afficherInfo() {
System.out.println(marque + " " + modele + " de " + annee);
}
}

Création et utilisation d'objets

Pour utiliser une classe, on doit créer des objets (ou instances) de cette classe :

public class Main {
public static void main(String[] args) {
// Création d'un premier objet Voiture
Voiture voiture1 = new Voiture();
voiture1.marque = "Tesla";
voiture1.modele = "Model 3";
voiture1.annee = 2023;
voiture1.couleur = "Rouge";

// Création d'un deuxième objet Voiture
Voiture voiture2 = new Voiture();
voiture2.marque = "Chevrolet";
voiture2.modele = "Bolt";
voiture2.annee = 2019;
voiture2.couleur = "Jaune";

// Utilisation des objets
voiture1.afficherInfo(); // Tesla Model 3 de 2023
voiture1.demarrer(); // La voiture démarre : Vroom!

voiture2.afficherInfo(); // Chevrolet Bolt de 2019
voiture2.klaxonner(); // Beep beep!
}
}

Observation importante : Nous avons deux objets distincts créés à partir de la même classe. Chaque objet a ses propres valeurs d'attributs.

Les Constructeurs : Simplifier la Création

Actuellement, créer un objet demande plusieurs lignes. Les constructeurs permettent d'initialiser un objet lors de sa création.

Constructeur de base

public class Voiture {
String marque;
String modele;
int annee;
String couleur;

// Constructeur
public Voiture(String marque, String modele, int annee, String couleur) {
this.marque = marque;
this.modele = modele;
this.annee = annee;
this.couleur = couleur;
}

public void demarrer() {
System.out.println("La voiture démarre : Vroom!");
}

public void afficherInfo() {
System.out.println(marque + " " + modele + " de " + annee + " (" + couleur + ")");
}
}

Utilisation simplifiée :

// Maintenant, création en une seule ligne !
Voiture voiture1 = new Voiture("Tesla", "Model 3", 2023, "Rouge");
Voiture voiture2 = new Voiture("Chevrolet", "Bolt", 2019, "Jaune");

voiture1.afficherInfo(); // Tesla Model 3 de 2023 (Rouge)

Le mot-clé this

Dans le constructeur, vous voyez this.marque = marque;. Que signifie this ?

  • this fait référence à l'objet actuel (l'instance en cours de création)
  • this.marque = l'attribut de l'objet
  • marque (sans this) = le paramètre du constructeur

Pourquoi utiliser this ? Pour distinguer entre l'attribut de la classe et le paramètre du constructeur quand ils ont le même nom.

// Sans this (ambigu)
public Voiture(String marque, String modele) {
marque = marque; // MAUVAIS : Assigne le paramètre à lui-même !
}

// Avec this (clair)
public Voiture(String marque, String modele) {
this.marque = marque; // BON : Assigne le paramètre à l'attribut de l'objet
}

Instanciation d'Objets en Mémoire

Comprendre comment les objets sont créés en mémoire :

Quand vous écrivez :

Voiture maVoiture = new Voiture("Tesla", "Model 3", 2023, "Rouge");
  1. new Voiture(...) : Crée un nouvel objet en mémoire
  2. Constructeur : Initialise les attributs de l'objet
  3. maVoiture : Variable qui contient une référence vers l'objet (comme une adresse)

Encapsulation et Modificateurs d'Accès

Jusqu'à présent, nos attributs sont publics par défaut, ce qui signifie que n'importe qui peut les modifier :

Voiture maVoiture = new Voiture("Tesla", "Model 3", 2023, "Rouge");
maVoiture.annee = -500; // ATTENTION : année négative !

La solution : Encapsulation

L'encapsulation consiste à :

  1. Rendre les attributs privés (private)
  2. Fournir des méthodes publiques contrôlées pour y accéder

Modificateurs d'accès

ModificateurAccèsDescription
publicPartoutAccessible depuis n'importe quelle classe
privateClasse uniquementAccessible uniquement à l'intérieur de la classe
protectedPackage + sous-classesAccessible dans le package et les classes héritées
(aucun)PackageAccessible dans le même package uniquement

Accesseurs (Getters) et Mutateurs (Setters)

Avec des attributs privés, on utilise des méthodes publiques pour lire et modifier les valeurs :

public class Voiture {
// Attributs PRIVÉS
private String marque;
private String modele;
private int annee;
private String couleur;
private int vitesse;

// Constructeur
public Voiture(String marque, String modele, int annee, String couleur) {
this.marque = marque;
this.modele = modele;
this.annee = annee;
this.couleur = couleur;
this.vitesse = 0; // Vitesse initiale
}

// GETTERS (accesseurs) - pour LIRE les valeurs
public String getMarque() {
return this.marque;
}

public String getModele() {
return this.modele;
}

public int getAnnee() {
return this.annee;
}

public int getVitesse() {
return this.vitesse;
}

// SETTERS (mutateurs) - pour MODIFIER les valeurs avec VALIDATION
public void setAnnee(int annee) {
if (annee > 1885 && annee <= 2026) { // Validation
this.annee = annee;
} else {
System.out.println("Année invalide !");
}
}

public void setVitesse(int vitesse) {
if (vitesse >= 0 && vitesse <= 300) { // Validation
this.vitesse = vitesse;
} else {
System.out.println("Vitesse invalide !");
}
}

// Méthodes métier
public void accelerer() {
if (this.vitesse < 300) {
this.vitesse += 10;
System.out.println("La voiture accélère. Vitesse : " + this.vitesse + " km/h");
}
}

public void freiner() {
if (this.vitesse > 0) {
this.vitesse -= 10;
System.out.println("La voiture freine. Vitesse : " + this.vitesse + " km/h");
}
}

public void demarrer() {
System.out.println("La " + this.marque + " " + this.modele + " démarre !");
}

public void afficherInfo() {
System.out.println(this.marque + " " + this.modele + " de " + this.annee +
" (" + this.couleur + ") - Vitesse : " + this.vitesse + " km/h");
}
}

Utilisation avec encapsulation

public class Main {
public static void main(String[] args) {
Voiture maVoiture = new Voiture("Tesla", "Model 3", 2023, "Rouge");

// Accès via getters
System.out.println("Marque : " + maVoiture.getMarque());
System.out.println("Vitesse initiale : " + maVoiture.getVitesse());

// Modification via setters avec validation
maVoiture.accelerer(); // Vitesse : 10 km/h
maVoiture.accelerer(); // Vitesse : 20 km/h
maVoiture.setVitesse(50); // OK

maVoiture.setVitesse(-10); // MAUVAIS : Vitesse invalide !
maVoiture.setVitesse(500); // MAUVAIS : Vitesse invalide !

maVoiture.afficherInfo();

// ERREUR DE COMPILATION : attributs privés
// maVoiture.vitesse = -100; // Ne compile pas !
}
}

Avantages de l'encapsulation

  1. Protection des données : Empêche les modifications incorrectes
  2. Validation : Contrôle les valeurs assignées
  3. Flexibilité : Possibilité de changer l'implémentation interne sans affecter le code externe
  4. Maintenabilité : Code plus facile à déboguer et à maintenir

Surcharge de Constructeurs

On peut avoir plusieurs constructeurs dans une classe avec des paramètres différents :

public class Voiture {
private String marque;
private String modele;
private int annee;
private String couleur;

// Constructeur complet
public Voiture(String marque, String modele, int annee, String couleur) {
this.marque = marque;
this.modele = modele;
this.annee = annee;
this.couleur = couleur;
}

// Constructeur simplifié (couleur par défaut)
public Voiture(String marque, String modele, int annee) {
this(marque, modele, annee, "Blanc"); // Appelle l'autre constructeur
}

// Constructeur minimal
public Voiture(String marque, String modele) {
this(marque, modele, 2024, "Blanc"); // Appelle l'autre constructeur
}

// Constructeur par défaut
public Voiture() {
this("Inconnu", "Inconnu", 2024, "Blanc");
}

public void afficherInfo() {
System.out.println(marque + " " + modele + " de " + annee + " (" + couleur + ")");
}
}

Utilisation :

Voiture v1 = new Voiture("Tesla", "Model 3", 2023, "Rouge");
Voiture v2 = new Voiture("Honda", "Civic", 2022); // Blanc par défaut
Voiture v3 = new Voiture("Toyota", "Corolla"); // 2024, Blanc
Voiture v4 = new Voiture(); // Toutes les valeurs par défaut

v1.afficherInfo(); // Tesla Model 3 de 2023 (Rouge)
v2.afficherInfo(); // Honda Civic de 2022 (Blanc)
v3.afficherInfo(); // Toyota Corolla de 2024 (Blanc)
v4.afficherInfo(); // Inconnu Inconnu de 2024 (Blanc)

Note : this(...) dans un constructeur appelle un autre constructeur de la même classe. Cela évite la duplication de code.

Exemple Complet : Gestion d'un Compte Bancaire

Voici un exemple complet qui rassemble tous les concepts :

public class CompteBancaire {
// Attributs privés
private String numeroCompte;
private String titulaire;
private double solde;

// Constructeur
public CompteBancaire(String numeroCompte, String titulaire, double soldeInitial) {
this.numeroCompte = numeroCompte;
this.titulaire = titulaire;
this.solde = (soldeInitial >= 0) ? soldeInitial : 0;
}

// Constructeur avec solde initial par défaut
public CompteBancaire(String numeroCompte, String titulaire) {
this(numeroCompte, titulaire, 0);
}

// Getters
public String getNumeroCompte() {
return this.numeroCompte;
}

public String getTitulaire() {
return this.titulaire;
}

public double getSolde() {
return this.solde;
}

// Méthodes métier
public void deposer(double montant) {
if (montant > 0) {
this.solde += montant;
System.out.println("Dépôt de " + montant + "$ effectué.");
} else {
System.out.println("Montant invalide.");
}
}

public void retirer(double montant) {
if (montant > 0 && montant <= this.solde) {
this.solde -= montant;
System.out.println("Retrait de " + montant + "$ effectué.");
} else {
System.out.println("Retrait impossible : montant invalide ou solde insuffisant.");
}
}

public void afficherInfo() {
System.out.println("Compte " + this.numeroCompte +
" - " + this.titulaire +
" - Solde : " + this.solde + "$");
}
}

Programme principal :

public class Main {
public static void main(String[] args) {
CompteBancaire compte1 = new CompteBancaire("12345", "Alice Dupont", 1000);
CompteBancaire compte2 = new CompteBancaire("67890", "Bob Martin");

compte1.afficherInfo();
compte1.deposer(500);
compte1.retirer(200);
compte1.afficherInfo();

compte2.afficherInfo();
compte2.deposer(1500);
compte2.retirer(2000); // Échec : solde insuffisant
compte2.afficherInfo();
}
}

Bonnes Pratiques

  1. Toujours rendre les attributs private (principe d'encapsulation)
  2. Fournir des getters/setters pour l'accès contrôlé
  3. Valider les données dans les setters et constructeurs
  4. Utiliser des noms significatifs pour les classes, méthodes et attributs
  5. Une classe = une responsabilité (principe de responsabilité unique)
  6. Documenter avec JavaDoc (vu au chapitre précédent)
  7. Utiliser this pour clarifier quand nécessaire